package storyteller;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Hashtable;

public class FlashAnalyzer {

  //`````````````````````
  // STATIC VARIABLES
  //`````````````````````  
  
  private static final String TEXT_COMMAND = "text";
  private static final String END_TEXT_COMMAND = "endtext";
  private static final String LABEL_COMMAND = "label";
  private static final String END_LABEL_COMMAND = "endlabel";
  private static final String IMAGE_COMMAND = "image";
  private static final String PLAY_COMMAND = "play";
  private static final String STOP_COMMAND = "stop";
  private static final String VIDEO_COMMAND = "video";
  private static final String PROPERTIES_PREFIX = "@";
  private static final String DEFAULT_PROPERTY_NAME = "@default";  

  //`````````````````````
  // MEMBER VARIABLES
  //`````````````````````  
  
  ArrayList<FlashAction> _allActions;
  int _nextAction;
  boolean _isMultiText;
  boolean _isMultiLabel;
  ArrayList<String> _multipleInputs;
  boolean _isStrict;
  
  private String _currentProperties;
  private Hashtable<String,String> _propertiesLookup;
  private File _relativeTo;
  private MediaDownloader _downloader;
  
  //`````````````````````
  // CONSTRUCTOR
  //`````````````````````    
  
  public FlashAnalyzer()
  {
    init(null,true);
  }
  
  public FlashAnalyzer(String tmpDirectory, boolean isStrict)
  {
    init(tmpDirectory, isStrict);
  }

  private void init(String tmpDirectory, boolean isStrict)
  {
    _isStrict = isStrict;
    
    _allActions = new ArrayList<FlashAction>();
    _nextAction = 0;
    _isMultiText = false;
    _isMultiLabel = false;
    _multipleInputs = null;
    
    _currentProperties = null;
    _propertiesLookup = new Hashtable<String,String>();
    _relativeTo = null;
    
    _downloader = new MediaDownloader(tmpDirectory);
    
  }
  
  
  
  //``````````````````````
  // INTERFACE
  //``````````````````````

  public static int slideDuration(String duration, int defaultDuration)
  {
    try
    {
      if (duration.endsWith("sec"))
      {
        duration = duration.substring(0,duration.length() - 3).trim();
      }
      return Integer.parseInt(duration);
    }
    catch (Exception e)
    {
      return defaultDuration;
    }
  }  
  
  public void removeTemporaryFiles()
  {
    _downloader.delete();
  }
  
  public FlashAction[] parse(String filename) 
  {
    try 
    {
      BufferedReader in = new BufferedReader(new FileReader(filename));
      String input;
      while ((input = in.readLine()) != null) 
      {
        // Ignore blank lines
        if(input.trim().length() == 0)
        {
          continue;
        }
        analyze(input);
      }
      in.close();
      return actions();
    } 
    catch (Exception e) 
    {
      e.printStackTrace();
      throw new RuntimeException("Unable to read ["+ filename +"]", e);
    }    
  }   
  
  public FlashAction nextAction()
  {
    if (_nextAction < _allActions.size())
    {
      return _allActions.get(_nextAction++);
    }
    else
    {
      return null;
    }
  }
  
  public void analyzeAll(String input, File relativeTo)
  {
    String[] allInputs = input.split("\n");
    for (int i=0; i<allInputs.length; i++)
    {
      analyze(allInputs[i],relativeTo);
    }
  }
  
  public boolean analyze(String input) 
  {
    return analyze(input,null);
  }

  public FlashAction[] actions()
  {
    if (_allActions.size() == 0)
    {
      return new FlashAction[] {FlashAction.createEmpty(_currentProperties)};  
    }
    else
    {
      return _allActions.toArray(new FlashAction[_allActions.size()]);        
    }
  }
  
  public String propertiesOf(String key)
  {
    return _propertiesLookup.get(key);
  }

  
  //`````````````````````
  // PRIVATE METHODS
  //`````````````````````    
  
  private boolean analyze(String input, File relativeTo)
  {
    _relativeTo = relativeTo;
    
    if (input == null || input.trim().equals(""))
    {
      return false;
    }
    input = input.trim();
    
    if (input.startsWith(PROPERTIES_PREFIX) && input.indexOf("{") != -1 && input.indexOf("}") != -1)
    {
      input = handleProperties(input);
      if (input.length() == 0)
      {
        return false;
      }
    }
    else
    {
      input = removePropertiesFrom(input);  
    }
    
 
    if (_isMultiText)
    {
      return handleMultiTextInput(input);
    }
    else if (_isMultiLabel)
    {
      return handleMultiLabelInput(input);
    }
    else if (input.startsWith(TEXT_COMMAND))
    {
      return handleTextInput(input,true);
    }
    else if (input.startsWith(LABEL_COMMAND))
    {
      return handleLabelInput(input);
    }
    else if (input.startsWith(IMAGE_COMMAND))
    {
      return handleImageInput(input);
    }
    else if (input.startsWith(PLAY_COMMAND))
    {
      return handlePlay(input);
    }
    else if (input.startsWith(STOP_COMMAND))
    {
      return handleStop();
    }
    else if(input.startsWith(VIDEO_COMMAND))
    {
      return handleVideo(input);
    }
    else
    {
      return handleTextInput(input,false);
    }
  }  
  
   
  private boolean handleTextInput(String input, boolean isExplicit)
  {
    if (input.trim().equals(TEXT_COMMAND))
    {
      _isMultiText = true;
      _multipleInputs = new ArrayList<String>();
      return false;
    }
    
    String text = input;
    if (isExplicit)
    {
      text = input.substring(TEXT_COMMAND.length() + 1).trim();
    }
    _allActions.add(FlashAction.createText(text,_currentProperties));
    return true;
  }  
  
  private boolean handleLabelInput(String input)
  {
    if (input.equals(LABEL_COMMAND))
    {
      _isMultiLabel = true;
      _multipleInputs = new ArrayList<String>();
      return false;
    }
    else
    {
      String text = input.substring(LABEL_COMMAND.length() + 1).trim();  
      _allActions.add(FlashAction.createLabel(text,_currentProperties));
      return true;
    }
  }
  
  private String handleProperties(String input)
  {
    int splitIndex = input.indexOf(" ");
    
    if (splitIndex == -1)
    {
      return input;
    }
    
    String propertyName = input.substring(0,splitIndex);
    String newInput = removePropertiesFrom(input.substring(splitIndex + 1));
    if (_currentProperties != null)
    {
      _propertiesLookup.put(propertyName, _currentProperties);  
    }
    return newInput;
  }
  
  private boolean handleMultiTextInput(String input)
  {
    if (input.trim().equals(END_TEXT_COMMAND))
    {
      _isMultiText = false;
      _allActions.add(FlashAction.createMultiText(_multipleInputs,_currentProperties));
      _multipleInputs = null;
      return true;
    }
    else
    {
      _multipleInputs.add(input);
      return false;
    }
  }
  
  private boolean handleMultiLabelInput(String input)
  {
    if (input.equals(END_LABEL_COMMAND))
    {
      _isMultiLabel = false;
      _allActions.add(FlashAction.createMultiLabel(_multipleInputs,_currentProperties));
      _multipleInputs = null;
      return true;
    }
    else
    {
      _multipleInputs.add(input);
      return false;
    }
  }
  
  
  // Extract command, filename and label from the text, which will look like this:
  // "image my-image.jpg my label for this image"     
  private boolean handleImageInput(String input)
  {
    String filename = null;

    if (input.trim() == IMAGE_COMMAND)
    {
      filename = "";
    }
    else
    {
      int startQuote = input.indexOf('"');
      int endIndex = -1;
      if (startQuote != -1)
      {
        int endQuote = input.indexOf('"',startQuote+1);
        if (endQuote != -1)
        {
          int startIndex = startQuote + 1;
          endIndex = endQuote;
          filename = input.substring(startIndex,endIndex);
        }
      }
      else
      {
        int delim = input.indexOf(' ', IMAGE_COMMAND.length() + 1);
        endIndex = delim;
        if(delim==-1) delim = input.length();
        filename = input.substring(IMAGE_COMMAND.length() + 1, delim).trim();
      }
      
      String label = endIndex != -1 ? input.substring(endIndex + 1).trim() : null;
      
      boolean wasImageTagged = label != null && label.length() > 0;
      if (wasImageTagged)
      {
        _allActions.add(FlashAction.createLabel(label,_currentProperties));
      }
      
      filename = rationalizeFile(filename);
    }
    
    FlashAction imgAction = null;
    if (_isStrict)
    {
      imgAction = createUrlableImage(filename,_currentProperties);
    }
    else
    {
      imgAction = FlashAction.createImage(filename,_currentProperties);
    }
    
    // Add the action for displaying the image
    _allActions.add(imgAction);
    return true;  
  }
 
  private FlashAction createUrlableImage(String potentialUrl, String properties)
  {
    File file = new File(potentialUrl);
    if (file.exists())
    {
      return FlashAction.createImage(potentialUrl,properties);
    }
    else
    {
      String localFilename = _downloader.get(potentialUrl);
      if (localFilename == null)
      {
        return FlashAction.createUnknownImage(potentialUrl,properties);
      }
      else
      {
        return FlashAction.createUrlImage(potentialUrl,localFilename,properties);
      }
    }
  }
  
  private boolean handlePlay(String input)
  {
     if(input.trim().equals(PLAY_COMMAND))
     {
       return false;
     }
     else
     {
       String filepath = input.substring(PLAY_COMMAND.length() + 1).trim();
       filepath = rationalizeFile(filepath);
       
       FlashAction action = null;
       if (_isStrict && isUrl(filepath))
       {
         String urlFileanem = filepath;
         String localFilename = _downloader.get(urlFileanem);
         action = FlashAction.createUrlPlay(urlFileanem, localFilename, _currentProperties);
       }
       else
       {
         action = FlashAction.createPlay(filepath,_currentProperties);  
       }
       
       _allActions.add(action);
       
     }
     
     return true;  
  }
  
  private boolean handleStop()
  {
     _allActions.add(FlashAction.createStop());
     return true;
  }
  
  private boolean handleVideo(String input)
  {
    // TODO stop audio
    
    if(input.trim().equals(VIDEO_COMMAND))
    {
      return false;
    }
    else
    {
      String filepath = input.substring(VIDEO_COMMAND.length() + 1).trim();  
      _allActions.add(FlashAction.createVideo(filepath,_currentProperties));
    }
    
    return false;
  }
  
  private String removeReferencePropertiesFrom(String input)
  {
    if (input == null)
    {
      return input;
    }
    
    int startIndex = input.indexOf("@");
    int endIndex = input.indexOf(" ",startIndex);
        
    if (startIndex == -1)
    {
      return input;
    }
    else if (endIndex == -1)
    {
      endIndex = input.length();
    }
   
    String lookupName = input.substring(startIndex,endIndex);
    _currentProperties = _propertiesLookup.get(lookupName);
    
    String newInput = input.substring(0,startIndex);
    if (endIndex < input.length() - 2)
    {
      newInput += input.substring(endIndex,input.length());
    }

    return newInput.trim();
  }
  
  private String removeInlinePropertiesFrom(String input)
  {
    if (input == null)
    {
      return input;
    }
    
    int startIndex = input.indexOf("{");
    int endIndex = input.indexOf("}") + 1;
    
    if (startIndex == -1 || endIndex == -1)
    {
      if (!isParallelAction())
      {
        setDefaultPropertyAsRequired();
      }
      return input;
    }
   
    String properties = input.substring(startIndex,endIndex);
    mergePropertiesWithDefault(properties);
    
    String newInput = input.substring(0,startIndex);
    if (endIndex < input.length() - 1)
    {
      newInput += input.substring(endIndex,input.length());
    }

    return newInput.trim();
  }
  
  private String removePropertiesFrom(String input)
  {
    if (input.contains("@"))
    {
      return removeReferencePropertiesFrom(input);
    }
    else
    {
      return removeInlinePropertiesFrom(input);
    }
  }
  
  private void mergePropertiesWithDefault(String newProperties)
  {
    
    String defaultProperties = _propertiesLookup.get(DEFAULT_PROPERTY_NAME);
    if (defaultProperties == null)
    {
      _currentProperties = newProperties;
      return;
    }
    else if (_currentProperties == null)
    {
      _currentProperties = defaultProperties;
      return;
    }
    else
    {
      _currentProperties = defaultProperties + newProperties; 
    }
  }
  
  private void setDefaultPropertyAsRequired()
  {
    if (_isMultiLabel || _isMultiText)
    {
      return;
    }
    _currentProperties = _propertiesLookup.get(DEFAULT_PROPERTY_NAME);    
  }
  
  private boolean isParallelAction()
  {
    int index = _allActions.size() - 1;
    if (index == -1)
    {
      return false;
    }
    else
    {
      FlashActionType lastType = _allActions.get(index).Type;
      return lastType == FlashActionType.MultiLabel || lastType == FlashActionType.Label;
    }
  }
  
  private String rationalizeFile(String filename)
  {
    if (_relativeTo == null || isUrl(filename))
    {
      return filename;
    }
    else if (_relativeTo.isDirectory())
    {
      return (new File(_relativeTo.getAbsolutePath(),filename)).getAbsolutePath();
    }
    else
    {
      return (new File(_relativeTo.getParentFile().getAbsolutePath(),filename)).getAbsolutePath();
    }
  }
  
  private boolean isUrl(String filename)
  {
    return filename.toLowerCase().startsWith("http");  
  }
  
}
